package gui;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.metal.*;

import game.*;
import game.buildings.*;
import game.resources.*;
import game.saints.*;
import gui.dialog.*;
import gui.event.*;
import gui.menu.*;
import main.*;

/**
 *	@(#)GameWindow.java
 *
 *	The <code>GameWindow</code> class is the graphic implementation of
 *	the Antiquity game, the so-called view. It has to implement the
 *	Controllable interface which defines the methods the view has to
 *	implement. If you are changing the graphic implementation, be sure
 *	to implement all the needed game object observers in analogy to the
 *	current implementation. This is not ensured by any interfaces.
 *
 *	@author Grzegorz Kobiela
 *	@version 1.03 2008/6/8
 */
public final class GameWindow
extends JFrame
{
	//================================================================================================= COMPONENTS

	/** The menus. */
	private ArrayList<JMenu> menus;

	/** The tabs. */
	private JTabbedPane tabbedPane;

	/** The landscape container. */
	private JLayeredPane landPane;

	/** The landscape menu listener. */
	private MouseListener landMenuListener;

	/** The city and fisherman buttons. */
	private GameButton cityButton, fishermanButton;

	/** The control button. */
	private JButton controlButton;

	/** The control button timer. */
	private javax.swing.Timer timer;

	/** The city containers. */
	private Hashtable<Player, JLayeredPane> cityPanes;

	/** The trade buttons. */
	private Hashtable<Player, JButton> tradeButtons = new Hashtable<Player, JButton>();

	/** The status bars. */
	private ArrayList<JLabel> statusBars = new ArrayList<JLabel>();

	/** The saints panels. */
	private Hashtable<SaintType, JPanel> saintsPanels;

	/** True if a land building has not been placed, yet. */
	private boolean unplaced = false;

	/** True if the land menu is active. */
	private boolean showLandMenu = false;

	/** True if the game guidance is active. */
	private boolean guide = false;

	//================================================================================================= GAME PREPARATION METHODS

	/** The bounds of the window. */
	private static final Rectangle BOUNDS = new Rectangle();

	/** True if the window is smaller than the preferred size. */
	private static final boolean SMALLER;

	static {
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		Dimension windowSize = ((Rectangle)Antiquity.getProperty("WindowBounds")).getSize();
		SMALLER = (screenSize.width < windowSize.width || screenSize.height < windowSize.height);
		BOUNDS.width = Math.min(screenSize.width, windowSize.width);
		BOUNDS.height = Math.min(screenSize.height, windowSize.height);
		BOUNDS.x = (screenSize.width - BOUNDS.width) / 2;
		BOUNDS.y = (screenSize.height - BOUNDS.height) / 2;
	}

	/**
	 *	Creates a new <code>GameWindow</code> instance.
	 */
	public GameWindow() {
		super(Antiquity.get("title"));
		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		addWindowListener(
			new WindowAdapter() {
				public void windowClosing(WindowEvent e) {
					quit();
				}
			}
		);

		/* Set look and feel, and font */
		try {
			UIManager.setLookAndFeel(new MetalLookAndFeel());
		} catch(UnsupportedLookAndFeelException e) {
			System.out.println("Failed setting look and feel.");
		}
		Enumeration keys = UIManager.getDefaults().keys();
		while(keys.hasMoreElements()) {
			Object key = keys.nextElement();
			Object value = UIManager.get(key);
			if(value instanceof FontUIResource) {
				UIManager.put(key, new javax.swing.plaf.FontUIResource("SansSerif", Font.BOLD, 12));
				break;
			}
		}

		/* Create cover image */
		ImageIcon icon = GameUtilities.getIcon(null, "cover");
		final JLabel cover = new JLabel(icon);
		int w = icon.getIconWidth();
		int h = icon.getIconHeight();
		cover.setSize(w, h);
		cover.setLocation((getWidth() - w) / 2, (getHeight() - h) / 2);
		getContentPane().add(cover);

		/* Create the menu bar. */
		JMenuBar bar = new JMenuBar();
		setJMenuBar(bar);
		menus = new ArrayList<JMenu>();

		/* Get labels and mnemonics for menu 'game'. */
		ArrayList<String> labels = Antiquity.getMenu(Antiquity.MENU_GAME);
		ArrayList<Character> mnemonics = Antiquity.getMnemonics(Antiquity.MENU_GAME);
		int playerCount = Antiquity.getPlayerCount();
		int i = 0;

		/* Create the 'game' menu. */
		JMenu menu = new JMenu(labels.get(i));
		menu.setMnemonic(mnemonics.get(i));
		menus.add(menu);
		bar.add(menu);
		i++;

		/* Create the 'game' menu item 'start'. */
		JMenuItem item = new JMenuItem(labels.get(i), mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					JMenu menu = menus.get(Antiquity.MENU_GAME);
					int n = menu.getItemCount() - 1;
					for(int i = 0; i < n; i++) {
						JMenuItem item = menu.getItem(i);
						if(item != null) item.setEnabled(false);
					}
					getContentPane().remove(cover);
					Antiquity.startGame();
				}
			}
		);
		menu.add(item);
		i++;

		menu.addSeparator();

		/* Create the 'game' menu item '2-player'. */
		ButtonGroup group = new ButtonGroup();
		item = new JRadioButtonMenuItem(labels.get(i), playerCount == 2);
		item.setMnemonic(mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Antiquity.setPlayerCount(2);
				}
			}
		);
		group.add(item);
		menu.add(item);
		i++;

		/* Create the 'game' menu item '3-player'. */
		item = new JRadioButtonMenuItem(labels.get(i), playerCount == 3);
		item.setMnemonic(mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Antiquity.setPlayerCount(3);
				}
			}
		);
		group.add(item);
		menu.add(item);
		i++;

		/* Create the 'game' menu item '4-player'. */
		item = new JRadioButtonMenuItem(labels.get(i), playerCount == 4);
		item.setMnemonic(mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Antiquity.setPlayerCount(4);
				}
			}
		);
		group.add(item);
		menu.add(item);
		i++;

		menu.addSeparator();

		/* Create the 'game' menu item 'language'. */
		item = new JMenuItem(labels.get(i), mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					String[] options = Antiquity.getLanguages();
					Object choice = JOptionPane.showInputDialog(
						GameWindow.this,
						Antiquity.get("dlgLanguageMessage"),
						Antiquity.get("dlgLanguageTitle"),
						JOptionPane.QUESTION_MESSAGE,
						null,
						options,
						options[0]
					);
					if(choice != null) Antiquity.setLanguage((String)choice);
				}
			}
		);
		menu.add(item);
		i++;

		/* Create the 'game' menu item 'options'. */
		item = new JMenuItem(labels.get(i), mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					new OptionsDialog(GameWindow.this);
				}
			}
		);
		menu.add(item);
		i++;

		menu.addSeparator();

		/* Create the 'game' menu item 'quit'. */
		item = new JMenuItem(labels.get(i), mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					quit();
				}
			}
		);
		menu.add(item);

		/* Get labels and mnemonics for menu 'help'. */
		labels = Antiquity.getMenu(Antiquity.MENU_HELP);
		mnemonics = Antiquity.getMnemonics(Antiquity.MENU_HELP);
		i = 0;

		/* Create the 'help' menu. */
		menu = new JMenu(labels.get(i));
		menu.setMnemonic(mnemonics.get(i));
		bar.add(menu);
		i++;

		/* Create the 'help' menu item 'handling'. */
		item = new JMenuItem(labels.get(i), mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					JDialog howTo = new JDialog(GameWindow.this, Antiquity.get("help"), false);
					Rectangle bounds = (Rectangle)Antiquity.getProperty("HowToBounds");
					howTo.setLocation(
						getX() + (getWidth() - bounds.width) / 2,
						getY() + (getHeight() - bounds.height) / 2
					);
					howTo.setSize(bounds.getSize());
					JScrollPane sp = new JScrollPane(new JLabel(String.format(Antiquity.get("howTo"), bounds.width - 34)));
					howTo.getContentPane().add(sp);
					howTo.setVisible(true);
				}
			}
		);
		menu.add(item);
		i++;

		/* Create the 'help' menu item 'game guidance'. */
		item = new JCheckBoxMenuItem(labels.get(i));
		item.setMnemonic(mnemonics.get(i));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					guide = ((JCheckBoxMenuItem)e.getSource()).getState();
				}
			}
		);
		menu.add(item);
		i++;

		/* Display window */
		setBounds(BOUNDS);
		setResizable(false);
		setVisible(true);
	}

	/**
	 *	Displays a warning before closing the window and ending the game.
	 */
	private void quit() {
		int option = JOptionPane.showConfirmDialog(
			null,
			toHTML(Antiquity.get("errQuit")),
			Antiquity.get("errQuitTitle"),
			JOptionPane.YES_NO_OPTION
		);
		if(option == JOptionPane.YES_OPTION) {
			Antiquity.logQuit();
			System.exit(0);
		}
	}

	/**
	 *	Creates the components for the landscape and the players' cities.
	 *	The view decides which strategy to use to display the components.
	 *	This method needs not to create the individual components itself
	 *	as there are future methods which will do this job.
	 *	@param n the player count
	 */
	public void createSections(int n) {
		tabbedPane = new JTabbedPane();
		getContentPane().add(tabbedPane);
		cityPanes = new Hashtable<Player, JLayeredPane>();
	}

	/**
	 *	Creates the control components for each section.
	 */
	public void createControl(GameFlow flow) {
		int n = tabbedPane.getComponentCount();
		for(int i = 0; i < n; i++) {
			JLabel statusBar = new JLabel();
			statusBar.setBounds((Rectangle)Antiquity.getProperty("StatusBounds"));
			getTab(i).add(statusBar, JLayeredPane.DEFAULT_LAYER);
			statusBars.add(statusBar);
		}
		statusBars.trimToSize();
		flow.addObserver(
			new Observer() {
				public void update(Observable obs, Object obj) {
					if(obj instanceof Integer) {
						if(GameFlow.PHASE_PERFORM == (Integer)obj) {
							GameFlow flow = (GameFlow)obs;
							int phase = flow.getPhase().ordinal();
							int index = flow.getTurn() + 1;
							int n = tabbedPane.getComponentCount();
							for(int i = 0; i < n; i++) {
								Player player = Antiquity.getPlayer((i > 0 ? i : index) - 1);
								statusBars.get(i).setIcon(GameUtilities.createIcon(player));
								String msg = "Phase" + phase;
								if(i > 0 && i != index && phase != GameFlow.Phase.GAME_END.ordinal()) msg = "Waiting";
								if(i > 0 && !player.isActive() && !flow.isOver()) msg = "Inactive";
								setStatus(i, phase, String.format(Antiquity.get("msg" + msg), player.getName()));
							}
							tabbedPane.setSelectedIndex(flow.isLandPhase() ? 0 : index);
						}
						Antiquity.proceed((Integer)obj);
					}
				}
			}
		);
		ActionListener delay = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(controlButton != null) controlButton.setEnabled(true);
			}
		};
		timer = new javax.swing.Timer((Integer)Antiquity.getProperty("ControlDelay"), delay);
	}

	/**
	 *	Draws the landtiles on the appropriate component.
	 *	@param board the game board object
	 */
	public void createLandscape(final GameBoard board) {
		JLayeredPane tab = new JLayeredPane();
		landPane = new JLayeredPane() {
			{
				ToolTipManager.sharedInstance().registerComponent(this);
			}

			public Point getToolTipLocation(MouseEvent e) {
				Point location = e.getPoint();
				if(e.getSource() instanceof JLabel) {
					Point p = ((Component)e.getSource()).getLocation();
					location.translate(p.x, p.y);
				}
				Hexfield hex = board.getHexfield(LandSystem.getPoint(location));
				if(hex == null || hex.isDisabled() || !hex.isHill()) return null;
				return new Point(location.x, location.y + 20);
			}

			public String getToolTipText(MouseEvent e) {
				Point location = e.getPoint();
				if(e.getSource() instanceof JLabel) {
					Point p = ((Component)e.getSource()).getLocation();
					location.translate(p.x, p.y);
				}
				Hexfield hex = board.getHexfield(LandSystem.getPoint(location));
				if(hex == null || hex.isDisabled() || !hex.isHill()) return null;
				ResourceType type = board.getMountainRange(hex).getType();
				return String.format("%s (%s)", Antiquity.get("hill"), type != null ? Antiquity.get("res" + Resource.createResource(type)) : "\u2014");
			}
		};

		/* Create the landscape */
		for(Landtile tile: board.getLandtiles()) {
			/* Create landtile label */
			GameLabel label = new GameLabel(GameUtilities.createIcon(tile));
			Point p = LandSystem.getPixel(tile.getLocation());
			label.setLocation(p.x - label.getWidth() / 2, p.y - label.getHeight() / 2);
			landPane.add(label, Antiquity.getLayer("LandBackground"));

			/* Create highlight observer */
			tile.addObserver(new HighlightObserver(landPane));
			for(int i = 0; true; i++) {
				final Hexfield hex = tile.getHexfield(i);
				if(hex == null) break;
				hex.addObserver(new HighlightObserver(landPane));
				hex.addObserver(
					new Observer() {
						private MouseListener listener = new MouseAdapter() {
							public void mouseClicked(MouseEvent e) {
								/* Accept this hex only */
								Point location = e.getPoint();
								if(e.getSource() instanceof JLabel) {
									Point p = ((Component)e.getSource()).getLocation();
									location.translate(p.x, p.y);
								}
								if(!LandSystem.getPoint(location).equals(hex.getLocation())) return;

								/* Double click */
								if(e.getClickCount() > 1) {
									hex.setPolluted(true);
									return;
								}

								/* Right click */
								if(e.getButton() == MouseEvent.BUTTON3) {
									JPopupMenu menu = new JPopupMenu();
									JMenuItem item = new JMenuItem(Antiquity.get("cmPollute"));
									item.addActionListener(
										new ActionListener() {
											public void actionPerformed(ActionEvent e) {
												hex.setPolluted(true);
											}
										}
									);
									menu.add(item);
									menu.setVisible(true);
									menu.show(landPane, location.x, location.y);
									return;
								}
							}
						};
						private JLabel label = null;

						public void update(Observable obs, Object obj) {
							if(obj instanceof Integer) {
								/* Add pollution marker */
								if(obj.equals(Hexfield.POLLUTED)) {
									Pollution chit = new Pollution();
									label = GameUtilities.createLabel(chit, LandSystem.getPixel(hex.getLocation()));
									label.setToolTipText(GameUtilities.getDescription(chit, hex.getType()));
									label.addMouseListener(landMenuListener);
									landPane.add(label, Antiquity.getLayer("LandPollution"));
									hex.update(Antiquity.getCommand("Polluted"));
									Antiquity.log(String.format("%s has been polluted", hex));
									return;
								}

								/* Remove pollution marker */
								if(obj.equals(Hexfield.UNPOLLUTED)) {
									if(label == null) return;
									landPane.remove(label);
									landPane.invalidate();
									landPane.repaint();
									label = null;
									Antiquity.log(String.format("%s has been cleaned", hex));
									return;
								}

								/* Add pollution menu */
								if(obj.equals(Antiquity.getCommand("AddMenu"))) {
									landPane.addMouseListener(listener);
									if(hex.getExplorationChit() != null) hex.getExplorationChit().update(listener);
									if(hex.isGrass()) hex.update(listener);
									return;
								}

								/* Remove pollution menu */
								if(obj.equals(Antiquity.getCommand("RemoveMenu"))) {
									landPane.removeMouseListener(listener);
									if(hex.getExplorationChit() != null) hex.getExplorationChit().update(listener);
									if(hex.isGrass()) hex.update(listener);
									return;
								}
							}
						}
					}
				);
				if(hex.isForest()) {
					hex.addObserver(
						new Observer() {
							private JLabel grass = null;
							private MouseListener pollutionListener = null;

							public void update(Observable obs, Object obj) {
								/* Convert to grass land */
								if(obj instanceof LandType && obj.equals(LandType.GRASS)) {
									grass = GameUtilities.createLabel(new Grass(), LandSystem.getPixel(hex.getLocation()));
									grass.addMouseListener(landMenuListener);
									landPane.add(grass, Antiquity.getLayer("LandGrass"));
									Antiquity.log(String.format("%s has been converted to grass", hex));
									return;
								}

								/* Add/remove pollution listener */
								if(obj instanceof MouseListener) {
									if(grass == null) return;
									MouseListener listener = (MouseListener)obj;
									if(pollutionListener != null) {
										pollutionListener = null;
										grass.removeMouseListener(listener);
										return;
									} else {
										pollutionListener = listener;
										grass.addMouseListener(listener);
										return;
									}
								}
							}
						}
					);
				}
			}
		}
		landPane.setPreferredSize(((Rectangle)Antiquity.getProperty("LandBounds")).getSize());

		/* Create city button */
		cityButton = new GameButton();
		cityButton.setBounds((Rectangle)Antiquity.getProperty("CityBounds"));
		cityButton.setAlpha(Antiquity.getAlpha("Default"));
		cityButton.setToolTipText(GameUtilities.getDescription(new City(null)));
		cityButton.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if(checkUnplaced()) return;
					City city = (City)cityButton.getClientProperty("represents");
					if(Antiquity.checkPurchase(Antiquity.getPlayer(), city, false)) prepareLandBuilding(city);
				}
			}
		);
		landPane.add(cityButton, Antiquity.getLayer("LandButtons"));
		disableCityButton();

		/* Create fisherman button */
		Fisherman fisherman = new Fisherman();
		fishermanButton = new GameButton();
		fishermanButton.setBounds((Rectangle)Antiquity.getProperty("FishermanBounds"));
		fishermanButton.setAlpha(Antiquity.getAlpha("Default"));
		fishermanButton.setIcon(GameUtilities.createIcon(fisherman));
		fishermanButton.putClientProperty("represents", fisherman);
		fishermanButton.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if(checkUnplaced()) return;
					Fisherman fisherman = new Fisherman();
					if(Antiquity.checkPurchase(Antiquity.getPlayer(), fisherman, false)) {
						updateFishermanButton();
						prepareLandBuilding(fisherman);
					}
				}
			}
		);
		fishermanButton.setEnabled(false);
		landPane.add(fishermanButton, Antiquity.getLayer("LandButtons"));
		updateFishermanButton();

		/* Create land menu pane */
		final LandMenu landMenu = new LandMenu();
		landMenuListener = new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if(!showLandMenu) return;
				Point location = e.getPoint();
				if(e.getSource() instanceof JLabel) {
					Point p = ((Component)e.getSource()).getLocation();
					location.translate(p.x, p.y);
				}
				Hexfield hex = board.getHexfield(LandSystem.getPoint(location));
				if(hex == null || hex.isDisabled() || hex.isUsed() || checkUnplaced()) return;
				Player player = Antiquity.getPlayer();
				ArrayList<Hexfield> zoc = player.getZoneOfControl();
				boolean within = zoc.contains(hex);
				boolean polluted = hex.isPolluted();
				JMenuItem[] items = landMenu.getItems();

				/* Double click automatic action */
				if(e.getClickCount() > 1) {
					landMenu.setLocation(location);

					/* Build woodcutter */
					if(within && !polluted && hex.isForest()) {
						items[LandMenu.OPTION_WOODCUTTER].doClick();
						return;
					}

					/* Build mine */
					if(within && !polluted && hex.isHill()) {
						items[LandMenu.OPTION_MINE].doClick();
						return;
					}

					/* Build farm */
					if(within && !polluted && hex.isGrass()) {
						items[LandMenu.OPTION_FARM].doClick();
						return;

					}

					/* Use alchemy */
					if(polluted && player.hasBuilding(Alchemy.class) > 0) {
						items[LandMenu.OPTION_POLLUTION].doClick();
						return;
					}
				}

				/* Show land building menu */
				if(e.getButton() == MouseEvent.BUTTON3) {
					/* Enable menu items */
					for(JMenuItem item: items) item.setEnabled(false);
					boolean show = false, enable;

					/* Inn option */
					show |= (enable = (within && !hex.isWater() && player.hasBuilding(Brewery.class) > 0));
					items[LandMenu.OPTION_INN].setEnabled(enable);

					/* Woodcutter option */
					show |= (enable = (within && !polluted && hex.isForest()));
					items[LandMenu.OPTION_WOODCUTTER].setEnabled(enable);

					/* Mine option */
					show |= (enable = (within && !polluted && hex.isHill()));
					items[LandMenu.OPTION_MINE].setEnabled(enable);

					/* Farm option */
					show |= (enable = (within && !polluted && hex.isGrass()));
					items[LandMenu.OPTION_FARM].setEnabled(enable);

					/* Alchemy option */
					show |= (enable = (polluted && player.hasBuilding(Alchemy.class) > 0));
					items[LandMenu.OPTION_POLLUTION].setEnabled(enable);

					/* Show menu */
					if(show) landMenu.show(landPane, location);
				}
			}
		};
		landPane.addMouseListener(landMenuListener);

		/* Add landscape to scroll pane */
		JScrollPane sp = new JScrollPane(landPane);
		sp.getVerticalScrollBar().setUnitIncrement(50);
		sp.setViewportBorder(BorderFactory.createLoweredBevelBorder());
		sp.setBounds((Rectangle)Antiquity.getProperty("LandScrollBounds"));

		/* Add everything to tab */
		tab.add(sp, JLayeredPane.DEFAULT_LAYER);
		tab.setPreferredSize(((Rectangle)Antiquity.getProperty("TabBounds")).getSize());
		String title = Antiquity.get("tabLand");
		if(SMALLER) {
			JScrollPane scrollPane = new JScrollPane(tab);
			scrollPane.getHorizontalScrollBar().setUnitIncrement(50);
			scrollPane.getVerticalScrollBar().setUnitIncrement(50);
			scrollPane.setBounds((Rectangle)Antiquity.getProperty("LandScrollBounds"));
			tabbedPane.addTab(title, scrollPane);
		} else {
			tabbedPane.addTab(title, tab);
		}
	}

	/**
	 *	Draws the famine track and marker on the appropriate component.
	 *	@param famine the famine object
	 */
	public void createFamineTrack(Famine famine) {
		JLayeredPane tab = getTab(0);

		/* Create the famine track */
		for(int i = 0; i < 2; i++) {
			GameLabel famineTrack = new GameLabel(GameUtilities.createIcon(famine, i + 1));
			Point famineOffset = (Point)Antiquity.getProperty("FamineOffset");
			famineTrack.setLocation(famineOffset.x + i * famineTrack.getWidth(), famineOffset.y);
			tab.add(famineTrack, Antiquity.getLayer("FamineBackground"));
		}

		/* Create the famine marker */
		final GameLabel marker = GameUtilities.createMarker(new Marker(PlayerColor.WOOD));
		famine.addObserver(
			new Observer() {
				public void update(Observable obs, Object obj) {
					marker.setLocation(GameUtilities.getLocation(obs));
					marker.setToolTipText(GameUtilities.getDescription(obs));
				}
			}
		);
		tab.add(marker, Antiquity.getLayer("FamineMarkers"));

		/* Create the saints panels */
		saintsPanels = new Hashtable<SaintType, JPanel>();
		for(SaintType type: SaintType.values()) {
			JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
			panel.setBounds((Rectangle)Antiquity.getProperty(Saint.getSaint(type) + "Offset"));
			panel.setOpaque(false);
			tab.add(panel, Antiquity.getLayer("FamineMarkers"));
			saintsPanels.put(type, panel);
		}
	}

	/**
	 *	Draws the turn order markers on the appropriate component
	 *	for the player with the given index.
	 *	@param player the player object
	 *	@param index the index
	 */
	public void createTurnOrder(final GameFlow flow, final Player player, final int index) {
		final GameLabel label = GameUtilities.createMarker(new Marker(player.getColor()));
		final Observer observer = new Observer() {
			public void update(Observable obs, Object obj) {
				if(obj instanceof TurnOrder) {
					int pos = ((TurnOrder)obj).getPosition(index);
					label.setLocation(GameUtilities.getLocation(obj, pos));
					label.setToolTipText(GameUtilities.getDescription(obj, player.getName(), pos + 1));
				}
			}
		};
		flow.addObserver(observer);
		player.addObserver(
			new Observer() {
				public void update(Observable obs, Object obj) {
					if(obj instanceof Boolean && !(Boolean)obj) {
						label.setIcon((ImageIcon)label.getIcon(), Antiquity.getAlpha("Hidden"));
						tabbedPane.setIconAt(index + 1, GameUtilities.getIcon("Fire", player.getColor().toString()));
						obs.deleteObserver(this);
					}
				}
			}
		);
		getTab(0).add(label, Antiquity.getLayer("FamineMarkers"));
	}

	/**
	 *	Draws the exploration chit lying on the given hexfield
	 *	on the appropriate component.
	 *	@param hex the hexfield
	 */
	public void createExplorationChit(final Hexfield hex) {
		ExplorationChit chit = hex.getExplorationChit();
		final DraggableLabel label = GameUtilities.createLabel(chit, LandSystem.getPixel(hex.getLocation()));
		label.setToolTipText(GameUtilities.getDescription(chit, hex.getType()));
		label.addMouseListener(landMenuListener);
		landPane.add(label, Antiquity.getLayer("LandExploration"));
		final JPopupMenu menu = new JPopupMenu();
		JMenuItem item = new JMenuItem(Antiquity.get("cmExplore"));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					label.resetIcon();
					Antiquity.explore(hex.getExplorationChit());
					hex.setExplorationChit(null);
					hex.deactivate();
				}
			}
		);
		menu.add(item);
		final MouseListener listener = new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if(e.getClickCount() > 1) {
					((JMenuItem)menu.getComponent(0)).doClick();
					label.removeMouseListener(this);
				}
			}
		};
		chit.addObserver(
			new Observer() {
				private MouseListener pollutionListener = null;

				public void update(Observable obs, Object obj) {
					if(obj == null) {
						landPane.remove(label);
						landPane.invalidate();
						landPane.repaint();
						obs.deleteObserver(this);
						Antiquity.log("removing " + obs);
						return;
					}

					if(obj instanceof Boolean) {
						if((Boolean)obj) {
							hex.activate();
							label.setComponentPopupMenu(menu);
							label.addMouseListener(listener);
							return;
						} else {
							hex.deactivate();
							label.setComponentPopupMenu(null);
							label.removeMouseListener(listener);
							return;
						}
					}

					if(obj instanceof MouseListener) {
						MouseListener listener = (MouseListener)obj;
						if(pollutionListener != null) {
							pollutionListener = null;
							label.removeMouseListener(listener);
							return;
						} else {
							pollutionListener = listener;
							label.addMouseListener(listener);
							return;
						}
					}
				}
			}
		);
	}

	/**
	 *	Draws the citymaps of the given player and the containers
	 *	for the city buildings and resources.
	 *	@param player the player object
	 */
	public void createCitymaps(Player player) {
		JLayeredPane tab = new JLayeredPane();
		JLayeredPane cityLayer = new JLayeredPane();

		/* Create trade button */
		JButton tradeButton = new JButton(Antiquity.get("btnTrade"));
		tradeButton.setBounds((Rectangle)Antiquity.getProperty("TradeButtonBounds"));
		tradeButton.setToolTipText(Antiquity.get("ftTrade"));
		tradeButton.setEnabled(false);
		tradeButton.putClientProperty("belongs", player);
		tradeButton.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Player player = (Player)((JButton)e.getSource()).getClientProperty("belongs");

					/* Determine potential traders */
					ArrayList<Player> traders = new ArrayList<Player>();
					int n = Antiquity.getPlayerCount();
					for(int i = 0; i < n; i++) {
						Player trader = Antiquity.getPlayer(i);
						if(trader == player) continue;
						if(!trader.isActive()) continue;
						if(trader.getResources().size() == 0) continue;
						traders.add(trader);
					}

					/* Let player choose one */
					ArrayList<String> options = new ArrayList<String>();
					options.add(Antiquity.get("dlgTradeBank"));
					for(Player trader: traders) options.add(trader.getName());
					Object choice = JOptionPane.showInputDialog(
						GameWindow.this,
						String.format(Antiquity.get("dlgTradeMessage"), player.getName()),
						Antiquity.get("dlgTradeChoice"),
						JOptionPane.QUESTION_MESSAGE,
						GameUtilities.createIcon(player.getColor()),
						options.toArray(),
						options.get(0)
					);
					if(choice == null) return;
					Player partner = null;
					for(Player trader: traders) {
						if(choice.equals(trader.getName())) {
							partner = trader;
							break;
						}
					}
					new TradeDialog(GameWindow.this, player, partner);
				}
			}
		);
		cityLayer.add(tradeButton, Antiquity.getLayer("CityBackground"));
		tradeButtons.put(player, tradeButton);

		/* Create buildings scroll pane */
		JScrollPane bsp = new JScrollPane();
		bsp.setViewportBorder(BorderFactory.createLoweredBevelBorder());
		bsp.setBounds((Rectangle)Antiquity.getProperty("BuildScrollBounds"));
		cityLayer.add(bsp, JLayeredPane.DEFAULT_LAYER);

		/* Create houses scroll pane */
		JScrollPane hsp = new JScrollPane();
		hsp.setViewportBorder(BorderFactory.createLoweredBevelBorder());
		hsp.setBounds((Rectangle)Antiquity.getProperty("HouseScrollBounds"));
		cityLayer.add(hsp, JLayeredPane.DEFAULT_LAYER);

		/* Create resources scroll pane */
		JScrollPane rsp = new JScrollPane();
		rsp.setViewportBorder(BorderFactory.createLoweredBevelBorder());
		rsp.setBounds((Rectangle)Antiquity.getProperty("ResourceScrollBounds"));
		cityLayer.add(rsp, JLayeredPane.DEFAULT_LAYER);

		/* Complete tab */
		cityLayer.setBounds((Rectangle)Antiquity.getProperty("CityLayerBounds"));
		cityPanes.put(player, cityLayer);
		tab.add(cityLayer, JLayeredPane.DEFAULT_LAYER);
		tab.setPreferredSize(((Rectangle)Antiquity.getProperty("TabBounds")).getSize());
		String title = String.format(Antiquity.get("tabCity"), player.getName());
		ImageIcon icon = GameUtilities.createIcon(player);
		if(SMALLER) {
			JScrollPane scrollPane = new JScrollPane(tab);
			scrollPane.getHorizontalScrollBar().setUnitIncrement(50);
			scrollPane.getVerticalScrollBar().setUnitIncrement(50);
			scrollPane.setBounds((Rectangle)Antiquity.getProperty("LandScrollBounds"));
			tabbedPane.addTab(title, icon, scrollPane);
		} else {
			tabbedPane.addTab(title, icon, tab);
		}
	}

	/**
	 *	Draws the given citymap for the given player at the specified
	 *	position within the target component.
	 *	@param player the player
	 *	@param map the citymap
	 *	@param position the position
	 */
	public void createCitymap(Player player, Citymap citymap, int position) {
		final GameLabel label = new GameLabel(GameUtilities.createIcon(citymap));
		label.putClientProperty("belongs", player);
		label.setLocation(GameUtilities.getLocation(citymap, position));
		label.setEnabled(false);
		cityPanes.get(player).add(label, Antiquity.getLayer("CityBackground"));
		citymap.addObserver(
			new Observer() {
				public void update(Observable obs, Object obj) {
					if((Boolean)obj) {
						label.setEnabled(true);
						obs.deleteObservers();
					}
				}
			}
		);
	}

	/**
	 *	Draws the given city building for the given player within his
	 *	buildings container. The third argument specifies when the
	 *	building is created.
	 *	@param player the player
	 *	@param building the city building
	 *	@param setup provide true if invoked during setup
	 */
	public void createCityBuilding(final Player player, CityBuilding building, boolean setup) {
		JLayeredPane cityLayer = cityPanes.get(player);

		/* Create limited building buttons */
		if(building instanceof Limited && setup) {
			final GameButton button = new GameButton();
			button.setBounds((Rectangle)Antiquity.getProperty(building + "ButtonBounds"));
			button.setToolTipText(GameUtilities.getDescription(building, true));
			button.setEnabled(false);
			button.setAlpha(Antiquity.getAlpha("Default"));
			button.setIcon(GameUtilities.createIcon(building));
			button.putClientProperty("represents", building);
			cityLayer.add(button, Antiquity.getLayer("CityBackground"));
			button.addActionListener(
				new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						Building building = (Building)button.getClientProperty("represents");

						/* Build storage */
						if(building instanceof Storage) {
							/* Player has no wood to pay */
							if(player.hasResource(Wood.class) == 0) {
								showWarning(Antiquity.get("errNotEnoughResources"));
								return;
							}

							/* Get possible sizes */
							String delim = " x ";
							ArrayList<String> options = new ArrayList<String>();
							for(int i = 1; i <= 3; i++) {
								for(int j = 1; j <= 7; j++) {
									if(((Limited)building).getTiles() >= i * j && Antiquity.isBuildable(player, new Storage(2 * i, j), true)) {
										options.add((2 * i) + delim + j);
									}
								}
							}

							/* Player cannot build any storages */
							if(options.size() == 0) {
								showWarning(Antiquity.get("errNotEnoughSpace"));
								return;
							}

							/* Let player choose storage size */
							Object choice = JOptionPane.showInputDialog(
								GameWindow.this,
								Antiquity.get("dlgStorage"),
								building.toString(),
								JOptionPane.QUESTION_MESSAGE,
								GameUtilities.createIcon(new Storage()),
								options.toArray(),
								options.get(0)
							);

							/* Player did not choose anything */
							if(choice == null) return;

							/* Create storage with chosen size */
							String[] pair = ((String)choice).split(delim);
							((Storage)building).setSize(Integer.parseInt(pair[0]), Integer.parseInt(pair[1]));
						}

						Antiquity.checkPurchase(player, building, false);
					}
				}
			);
			building.addObserver(
				new Observer() {
					public void update(Observable obs, Object obj) {
						/* Update button tooltip */
						if(obj == null) {
							button.setToolTipText(GameUtilities.getDescription(obs, true));
							if(((Limited)obs).getTiles() == 0) {
								button.removeActionListener(button.getActionListeners()[0]);
								button.setEnabled(false);
								((Building)obs).deactivate();
								obs.deleteObservers();
							}
							return;
						}

						/* Enable or disable button */
						if(obj instanceof Integer) {
							if(obj.equals(Antiquity.getCommand("Enable"))) {
								button.setEnabled(true);
								return;
							}

							if(obj.equals(Antiquity.getCommand("Disable"))) {
								button.setEnabled(false);
								return;
							}
						}
					}
				}
			);

		/* Create regular building */
		} else {
			final DraggableLabel label = GameUtilities.createLabel(building, player, false);
			if(building instanceof House) label.setOpaqueIcon(GameUtilities.createIcon(building, true));
			if(setup) {
				label.setDraggable(false);
				label.setEnabled(false);
			}
			/* Save building location */
			label.addMouseListener(
				new MouseAdapter() {
					public void mouseReleased(MouseEvent e) {
						((Building)label.getClientProperty("represents")).update(CitySystem.getPoint(label.getLocation()));
					}
				}
			);
			cityLayer.add(label, Antiquity.getLayer(building instanceof Grave ? "CityGraves" : "CityBuildings"));
			final JPopupMenu menu;
			final MouseListener doubleClick;
			building.addObserver(
				new Observer() {
					public void update(Observable obs, Object obj) {
						if(obj == null) return;
						final CityBuilding building = (CityBuilding)obs;

						/* Adjust location */
						if(obj instanceof Point) {
							Point location = (Point)obj;
							building.setLocation(location);
							location = CitySystem.getPixel(location);
							Rectangle bounds = label.getBounds();
							bounds.setLocation(location);
							if(((Rectangle)Antiquity.getProperty("CityMapBounds")).contains(bounds)) {
								label.setLocation(location);
								if(building.isActive() && building.isEnabled()) {
									if(building instanceof Faculty || building instanceof University) Antiquity.updateFaculties(player);
								}
							} else {
								building.setLocation(null);
							}
							Antiquity.log(String.format("player %s moved %s to %s", player.getName(), building, location));
							return;
						}

						/* Freeze and unfreeze label */
						if(obj instanceof Integer) {
							if(obj.equals(CityBuilding.UNMOVABLE)) {
								Antiquity.log(String.format("player %s - %s frozen", player.getName(), building));
								label.setDraggable(false);
								return;
							}

							if(obj.equals(CityBuilding.MOVABLE)) {
								Antiquity.log(String.format("player %s - %s unfrozen", player.getName(), building));
								label.setDraggable(true);
								return;
							}
						}
					}
				}
			);
			if(building instanceof Grave) {
				/* Save initial location */
				((Grave)building).setRecentLocation(label.getLocation());

				/* Attachment listener */
				label.setAttachmentID(Grave.class);
				label.addAttachmentListener(
					new AttachmentAdapter() {
						private Grave grave = (Grave)label.getClientProperty("represents");

						private DraggableLabel getLabel(EventObject e) {
							return (DraggableLabel)e.getSource();
						}

						private CityBuilding getBuilding(EventObject e) {
							return (CityBuilding)getLabel(e).getClientProperty("represents");
						}

						/* Grave attached */
						public void componentAttached(AttachmentEvent e) {
							CityBuilding building = getBuilding(e);
							if(building == grave.getBlocked()) return;
							if(player.getSpace() > 0) {
								showWarning(Antiquity.get("errGraveFreeSpace"));
							} else if(grave.getBlocked() != null) {
								showWarning(Antiquity.get("errGraveIllegal"));
							} else if(building instanceof House) {
								showWarning(Antiquity.get("errGraveImmune"));
							} else {
								return;
							}
							label.detach();
							label.setLocation(grave.getRecentLocation());
						}

						/* Grave detached */
						public void componentDetached(AttachmentEvent e) {
							if(grave.getBlocked() != getBuilding(e)) return;
							if(Antiquity.getPhase() == GameFlow.Phase.CITY_BUILDING.ordinal()) {
								if(!Antiquity.checkPlacement(player, false)) {
									getLabel(e).attach(label);
									label.setLocation(grave.getRecentLocation());
								}
							}
						}
					}
				);

				/* Removal via double click */
				doubleClick = new MouseAdapter() {
					public void mouseClicked(MouseEvent e) {
						if(e.getClickCount() > 1) ((JMenuItem)label.getComponentPopupMenu().getComponent(0)).doClick();
					}
				};

				/* Removal via popup menu */
				menu = new JPopupMenu();
				JMenuItem item = new JMenuItem(Antiquity.get("cmGrave"));
				item.addActionListener(
					new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							((Grave)label.getClientProperty("represents")).update(null);
						}
					}
				);
				menu.add(item);

				/* Add observers */
				building.addObserver(
					new Observer() {
						public void update(Observable obs, Object obj) {
							/* Remove grave */
							if(obj == null) {
								obs.deleteObservers();
								Grave grave = (Grave)obs;
								player.removeBuilding(grave);
								GameObject blocked = grave.getBlocked();
								Antiquity.log(String.format("player %s removed grave from %s", player.getName(), blocked));

								/* Free city space */
								if(blocked != EmptySpace.getInstance()) {
									((CityBuilding)blocked).removeGrave(grave);
								} else {
									player.updateSpace(1);
								}

								/* Set hospital used */
								Hospital hospital = player.getBuildings(Hospital.class).get(0);
								hospital.getWorker().deactivate();
								hospital.setUsed(true);
								if(hospital.isUsed()) Antiquity.enableGraveRemoval(player, false);

								/* Remove grave label */
								JLayeredPane cityLayer = cityPanes.get(player);
								cityLayer.remove(label);
								cityLayer.invalidate();
								cityLayer.repaint();
								return;
							}

							/* Activate grave */
							if(obj instanceof Boolean && (Boolean)obj) {
								label.resetIcon();
								((Grave)obs).setMovable(false);
								return;
							}

							/* Add and remove removal options */
							if(obj instanceof Integer) {
								if(obj.equals(Antiquity.getCommand("AddMenu"))) {
									label.setComponentPopupMenu(menu);
									label.addMouseListener(doubleClick);
									return;
								}

								if(obj.equals(Antiquity.getCommand("RemoveMenu"))) {
									label.setComponentPopupMenu(null);
									label.removeMouseListener(doubleClick);
									return;
								}
							}

							/* Block field */
							if(obj instanceof GameObject) {
								label.setToolTipText(GameUtilities.getDescription(obs, obj));
							}
						}
					}
				);
			} else {
				/* Purchase via menu */
				menu = new BuildingMenu();

				/* Purchase with double click */
				doubleClick = new MouseAdapter() {
					public void mouseClicked(MouseEvent e) {
						if(e.getClickCount() > 1 && label.isDraggable()) ((JMenuItem)label.getComponentPopupMenu().getComponent(0)).doClick();
					}
				};
				label.addMouseListener(doubleClick);

				/* Add observers */
				building.addObserver(
					new Observer() {
						public void update(Observable obs, Object obj) {
							if(obj == null) return;
							final CityBuilding building = (CityBuilding)obs;

							/* Building bought/razed */
							if(obj instanceof Boolean) {
								int size = building.getFields().size();
								if((Boolean)obj) {
									Antiquity.log(String.format("player %s builds %s", player.getName(), building));
									if(building instanceof Faculty) Antiquity.updateFaculties(player);
									label.resetIcon();
									label.addAcceptedID(Grave.class);
									label.addAcceptedID(Resource.class);
									label.addAcceptedID(Worker.class);
									label.removeMouseListener(doubleClick);
									player.updateSpace(-size);
									return;
								} else {
									Antiquity.log(String.format("player %s razes %s", player.getName(), building));
									label.setIcon((ImageIcon)label.getIcon(), Antiquity.getAlpha("Default"));
									label.removeAcceptedID(Grave.class);
									label.removeAcceptedID(Resource.class);
									label.removeAcceptedID(Worker.class);
									label.addMouseListener(doubleClick);
									player.updateSpace(+size);
									Theology theology = player.getBuildings(Theology.class).get(0);
									theology.getWorker().deactivate();
									theology.setUsed(true);
									building.update(Antiquity.getCommand("AddMenu"));
									building.setMovable(true);
									return;
								}
							}

							/* Attach label */
							if(obj instanceof DraggableLabel) {
								DraggableLabel child = (DraggableLabel)obj;
								label.attach(child, child.getLocation());
								return;
							}

							/* Various commands */
							if(obj instanceof Integer) {
								if(obj.equals(Antiquity.getCommand("AddMenu"))) {
									((BuildingMenu)menu).updateCityMenu(building.isActive());
									if(!building.isActive()) {
										((JMenuItem)menu.getComponent(0)).addActionListener(
											new ActionListener() {
												public void actionPerformed(ActionEvent e) {
													Antiquity.checkPurchase(player, building, false);
												}
											}
										);
									}
									label.setComponentPopupMenu(menu);
									if(building instanceof Cathedral && player.hasBuilding(Theology.class) > 0) Antiquity.enableRazing(player, true);
									return;
								}

								if(obj.equals(Antiquity.getCommand("RemoveMenu"))) {
									label.setComponentPopupMenu(null);
									return;
								}

								if(obj.equals(Antiquity.getCommand("Enable"))) {
									label.setEnabled(true);
									return;
								}

								if(obj.equals(Antiquity.getCommand("Disable"))) {
									label.setEnabled(false);
									return;
								}

								if(obj.equals(Antiquity.getCommand("Back"))) {
									label.setLocation(GameUtilities.getLocation(building));
									building.setLocation(null);
									return;
								}
							}
						}
					}
				);

				/* Cathedral: add/remove raze option */
				if(building instanceof Cathedral) {
					building.addObserver(
						new Observer() {
							public void update(Observable obs, Object obj) {
								if(obj instanceof Integer) {
									if(obj.equals(Antiquity.getCommand("AddRazeOption"))) {
										final Cathedral cathedral = (Cathedral)obs;
										if(label.getComponentPopupMenu() == null) {
											menu.removeAll();
										} else {
											menu.addSeparator();
										}
										JMenuItem raze = new JMenuItem(Antiquity.get("cmRaze"));
										raze.addActionListener(
											new ActionListener() {
												public void actionPerformed(ActionEvent e) {
													if(Antiquity.checkUniversity(player)) cathedral.deactivate();
												}
											}
										);
										menu.add(raze);
										label.setComponentPopupMenu(menu);
										return;
									}

									if(obj.equals(Antiquity.getCommand("RemoveRazeOption"))) {
										if(menu.getComponentCount() > 1) {
											((Cathedral)obs).update(Antiquity.getCommand("AddMenu"));
										} else {
											label.setComponentPopupMenu(null);
										}
										return;
									}
								}
							}
						}
					);

				/* Perform appropriate actions after manning */
				} else if(building instanceof MannableBuilding) {
					building.addObserver(
						new Observer() {
							public void update(Observable obs, Object obj) {
								if(obj instanceof Worker) {
									MannableBuilding building = (MannableBuilding)obs;
									boolean manned = building.isManned();
									Antiquity.log(String.format("player %s mans %s: %s", player.getName(), building, manned));
									if(building instanceof Hospital) {
										Antiquity.enableGraveRemoval(player, manned);
									} else if(building instanceof Market) {
										tradeButtons.get(player).setEnabled(manned);
									} else if(building instanceof Theology) {
										Antiquity.enableRazing(player, manned);
									} else if(building instanceof University) {
										University university = (University)building;
										if(manned) {
											Antiquity.updateFaculties(player);
										} else {
											university.removeFaculties();
										}
									} else if(building instanceof Harbour || building instanceof Stables) {
										for(Hexfield hex: player.getZoneOfControl()) if(hex.isActive()) hex.deactivate();
										Antiquity.updateZoneOfControl(player);
										for(Hexfield hex: player.getZoneOfControl()) hex.activate();
									}
								}
							}
						}
					);
				}
			}
		}
	}

	/**
	 *	Draws the given resource of the given producer for the given player
	 *	on the given hexfield.
	 *	@param player the player
	 *	@param resource the resource
	 *	@param producer the producer
	 *	@param hex the hexfield
	 */
	public void createResource(final Player player, final Resource resource, final Producer producer, final Hexfield hex) {
		Antiquity.log(String.format("drawing %s (%s) at %s for player %s", resource, producer, hex, player.getName()));
		Point location = LandSystem.getPixel(hex.getLocation());
		final DraggableLabel label = GameUtilities.createLabel(resource, player, player.getName());
		label.setLocation(location.x - label.getWidth() / 2, location.y - label.getHeight() / 2);
		label.setDraggable(false);
		landPane.add(label, Antiquity.getLayer("LandResources"));
		final JPopupMenu menu = new JPopupMenu();
		JMenuItem item = new JMenuItem(Antiquity.get("cmHarvest"));
		item.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					resource.deactivate();
					resource.update(null);
				}
			}
		);
		menu.add(item);
		final MouseListener listener = new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if(e.getClickCount() > 1) {
					((JMenuItem)menu.getComponent(0)).doClick();
					label.removeMouseListener(this);
				}
			}
		};
		resource.addObserver(
			new Observer() {
				public void update(Observable obs, Object obj) {
					/* Resource was harvested */
					if(obj == null) {
						Antiquity.log(String.format("removing %s (%s) at %s for player %s", resource, producer, hex, player.getName()));
						landPane.remove(label);
						landPane.invalidate();
						landPane.repaint();
						Antiquity.harvest(player, producer, resource);
						resource.deleteObserver(this);
						hex.setUsed(false);
						return;
					}

					/* Add and remove harvest menu */
					if(obj instanceof Boolean) {
						if(hex.getLocation().equals(producer.getLocation())) return;
						if((Boolean)obj) {
							hex.activate();
							label.setComponentPopupMenu(menu);
							label.addMouseListener(listener);
						} else {
							hex.deactivate();
							label.setComponentPopupMenu(null);
							label.removeMouseListener(listener);
						}
					}
				}
			}
		);
	}

	/**
	 *	Draws the given resource for the given player within the harvest box.
	 *	@param player the player
	 *	@param res the resource
	 */
	public void createResource(final Player player, Resource resource) {
		Antiquity.log(String.format("drawing %s in harvest box for player %s", resource, player.getName()));
		final DraggableLabel label = GameUtilities.createLabel(resource, player);
		label.setAttachmentID(Resource.class);
		label.addMouseListener(
			new MouseAdapter() {
				public void mouseClicked(MouseEvent e) {
					if(e.getClickCount() > 1) {
						Resource resource = (Resource)label.getClientProperty("represents");
						int n = player.getResources(resource.getType()).size();
						showInformation(String.format(Antiquity.get("dlgResource"), n, Antiquity.get("res" + resource, n)));
					}
				}
			}
		);
		Rectangle rsb = (Rectangle)Antiquity.getProperty("ResourceScrollBounds");
		Point offset = (Point)Antiquity.getProperty("ResourceOffset");
		final Point step = (Point)Antiquity.getProperty("ResourceStep");
		final Point location = new Point(rsb.x + offset.x, rsb.y + offset.y);
		resource.addObserver(
			new Observer() {
				private JLayeredPane cityLayer = cityPanes.get(player);

				public void update(Observable obs, Object obj) {
					/* Rearrange resource */
					if(obj instanceof Integer) {
						ResourceType type = ((Resource)obs).getType();
						int i = type.ordinal();
						int j = (Integer)obj;
						if(j < 0) j = player.getResources(type).size() + j;
						label.detach();
						label.setLocation(location.x + i * step.x, location.y + j * step.y);
						cityLayer.add(label, Antiquity.getLayer("CityResources"));
						cityLayer.moveToFront(label);
					}

					/* Remove resource */
					if(obj instanceof Boolean && !(Boolean)obj) {
						Antiquity.log(String.format("removing %s from havest box for player %s", obs, player.getName()));
						label.detach();
						cityLayer.remove(label);
						cityLayer.invalidate();
						cityLayer.repaint();
						obs.deleteObserver(this);
						return;
					}
				}
			}
		);
		player.addResource(resource);
		resource.update(-1);
	}

	/**
	 *	Draws the given worker for the given player at the specified
	 *	position within the target component.
	 *	@param player the player
	 *	@param worker the worker
	 *	@param position the position
	 */
	public void createWorker(final Player player, Worker worker, int position) {
		Antiquity.log(String.format("drawing %s at position %s for player %s", worker, position, player.getName()));
		final DraggableLabel label = GameUtilities.createLabel(worker, player, position);
		label.setAttachmentID(Worker.class);
		label.addAcceptedID(Resource.class);
		label.addAttachmentListener(
			new AttachmentAdapter() {
				public void componentAttached(AttachmentEvent e) {
					Object object = ((DraggableLabel)e.getSource()).getClientProperty("represents");
					Worker worker = (Worker)label.getClientProperty("represents");
					if(object instanceof MannableBuilding) {
						MannableBuilding building = (MannableBuilding)object;
						if(!building.isEnabled()) {
							showWarning(Antiquity.get("errDisabled"));
							worker.update(null);
						} else if(building.isManned()) {
							showWarning(Antiquity.get("errManning"));
							worker.update(null);
						} else {
							building.setWorker(worker);
						}
					} else if(object instanceof Resource) {
						return;
					} else {
						showWarning(Antiquity.get("errNoManning"));
						worker.update(null);
					}
				}

				public void componentDetached(AttachmentEvent e) {
					Object object = ((DraggableLabel)e.getSource()).getClientProperty("represents");
					Worker worker = (Worker)label.getClientProperty("represents");
					if(object instanceof MannableBuilding) {
						MannableBuilding building = (MannableBuilding)object;
						if(building.getWorker() == worker) building.setWorker(null);
					}
				}
			}
		);
		final JLayeredPane cityPane = cityPanes.get(player);
		cityPane.add(label, Antiquity.getLayer("CityWorkers"));
		final Point location = label.getLocation();
		worker.addObserver(
			new Observer() {
				public void update(Observable obs, Object obj) {
					Worker worker = (Worker)obs;

					/* Worker returns from building */
					if(obj == null) {
						if(worker.isOnLand()) {
							landPane.remove(label);
							landPane.invalidate();
							landPane.repaint();
							cityPane.add(label, Antiquity.getLayer("CityWorkers"));
							worker.setOnLand(false);
							label.setToolTipText(GameUtilities.getDescription(worker));
						}
						label.detach();
						label.setLocation(location);
						return;
					}

					/* Freeze or unfreeze */
					if(obj instanceof Boolean) {
						label.setDraggable((Boolean)obj);
						return;
					}

					/* Send out on land or return */
					if(obj instanceof Point) {
						label.detach();
						cityPane.remove(label);
						cityPane.invalidate();
						cityPane.repaint();
						landPane.add(label, Antiquity.getLayer("LandWorkers"));
						Point location = (Point)obj;
						location.translate(-label.getWidth() / 2, -label.getHeight() / 2);
						label.setLocation(location);
						label.setDraggable(false);
						worker.setOnLand(true);
						return;
					}

					/* Update description */
					if(obj instanceof Producer) {
						label.setToolTipText(GameUtilities.getDescription(obj, ((Producer)obj).getResources().get(0)));
						return;
					}
				}
			}
		);
	}

	/**
	 *	Draws the given land building for the given player.
	 *	@param player the player
	 *	@param building the land building
	 */
	public void createLandBuilding(Player player, LandBuilding building) {
		Antiquity.log(String.format("drawing %s for player %s", building, player.getName()));
		DraggableLabel label = GameUtilities.createLabel(building, player);
		landPane.add(label, Antiquity.getLayer("LandBuildings"));
		landPane.moveToFront(label);
	}

	//================================================================================================= GAME FLOW METHODS

	/**
	 *	Begins the preparation phase.
	 */
	public void beginPreparation() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 0: Create the city tile and let the active
	 *	player move it to the available position of his choice.
	 *	Depending on the model implementation, you need to provide
	 *	components to proceed with the game.
	 */
	public void performPreparation() {
		enableCityButton();
		cityButton.doClick();
		disableCityButton();
	}

	/**
	 *	Finalizes the preparation phase.
	 */
	public void endPreparation() {}

	/**
	 *	Begins the all rise phase.
	 */
	public void beginAllRise() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 1: Move all workers of the active player
	 *	from his city buildings to their original location.
	 *	Depending on the model implementation, you need to provide
	 *	components to proceed with the game.
	 */
	public void performAllRise() {
		Antiquity.nextTurn();
	}

	/**
	 *	Ends the all rise phase.
	 */
	public void endAllRise() {}

	/**
	 *	Begins the city building phase.
	 */
	public void beginCityBuilding() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 2: Enable the unsold buildings of all players
	 *	and let Barbara players move their buildings. Enable the
	 *	manning and purchase of buildings.
	 *	Depending on the model implementation, you need to provide
	 *	components to proceed with the game.
	 */
	public void performCityBuilding() {
		for(Hexfield hex: Antiquity.getPlayer().getZoneOfControl()) hex.activate();
		addControlButton(Antiquity.getTurn() + 1);
		controlButton.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Player player = Antiquity.getPlayer();
					if(!Antiquity.checkPlacement(player, true)) return;
					Antiquity.freezeBuildings(player, true);
					for(Hexfield hex: player.getZoneOfControl()) if(hex.isActive()) hex.deactivate();
					Antiquity.log(String.format("player %s finished city phase", player.getName()));
					Antiquity.nextTurn();
				}
			}
		);
	}

	/**
	 *	Ends the city building phase.
	 */
	public void endCityBuilding() {
		removeControlButton();
		for(JButton button: tradeButtons.values()) button.setEnabled(false);
	}

	/**
	 *	Begins the turn order phase.
	 */
	public void beginTurnOrder() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 3: Vizualizes the new turn order.
	 */
	public void performTurnOrder() {
		Antiquity.nextTurn();
	}

	/**
	 *	Ends the turn order phase.
	 */
	public void endTurnOrder() {}

	/**
	 *	Begins the land building phase.
	 */
	public void beginLandBuilding() {
		if(guide) showGameGuidance(Antiquity.getPhase());
		showLandMenu = true;
	}

	/**
	 *	Performs phase 4: Enable each player to build as many land
	 *	buildings as he has manned cartshops.
	 */
	public void performLandBuilding() {
		final Player player = Antiquity.getPlayer();
		int n = player.hasBuilding(Cartshop.class);
		boolean cleaning = (player.hasBuilding(Alchemy.class) > 0);

		/* Continue if done */
		if(n == 0 && !cleaning) {
			Antiquity.nextTurn();
			return;
		}

		/* Show information */
		int phase = Antiquity.getPhase();
		String prefix = "msgPhase" + phase, text = "";
		if(cleaning) text += Antiquity.get(prefix + "Clean");
		if(n > 0) {
			if(text != "") text += " " + Antiquity.get("and") + " ";
			text += String.format(Antiquity.get(prefix + "Left"), n, Antiquity.get("building", n));
			if(player.hasBuilding(Biology.class) > 0) text += " " + Antiquity.get(prefix + "Free");
		}
		setStatus(0, phase, String.format(Antiquity.get(prefix + "Text"), player.getName(), text));

		/* Enable buttons and ZOC */
		if(n > 0) {
			enableCityButton();
			enableFishermanButton();
			for(Hexfield hex: player.getZoneOfControl()) if(!hex.isUsed()) hex.activate();
		}

		/* Create control button */
		addControlButton(0);
		controlButton.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if(checkUnplaced()) return;
					disableCityButton();
					disableFishermanButton();
					Player player = Antiquity.getPlayer();
					for(Cartshop cartshop: player.getBuildings(Cartshop.class)) if(!cartshop.isUsed()) cartshop.setUsed(true);
					if(player.hasBuilding(Alchemy.class) > 0) player.getBuildings(Alchemy.class).get(0).setUsed(true);
					Antiquity.log(String.format("player %s finished land phase via button", player.getName()));
					Antiquity.checkPlacement(null, null);
				}
			}
		);
	}

	/**
	 *	Ends the land building phase.
	 */
	public void endLandBuilding() {
		showLandMenu = false;
		disableCityButton();
		disableFishermanButton();
		removeControlButton();
	}

	/**
	 *	Begins the storage phase.
	 */
	public void beginStorage() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 5: Force each player to discard the resources
	 *	he cannot store. If he can't store anything, he should lose
	 *	all of his resources automatically.
	 */
	public void performStorage() {
		Antiquity.nextTurn();
	}

	/**
	 *	Ends the storage phase.
	 */
	public void endStorage() {}

	/**
	 *	Begins the harvest phase.
	 */
	public void beginHarvest() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 6: Let each player collect his resources.
	 *	Discard one of each resource if player is using forced labour.
	 */
	public void performHarvest() {}

	/**
	 *	Ends the harvest phase.
	 */
	public void endHarvest() {}

	/**
	 *	Begins the exploration phase.
	 */
	public void beginExploration() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 7: Let each player collect one exploration chit
	 *	lying within his zone of control.
	 */
	public void performExploration() {
		addControlButton(0);
		controlButton.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Antiquity.log(String.format("player %s doesn't want to explore", Antiquity.getPlayer().getName()));
					Antiquity.explore(null);
				}
			}
		);
	}

	/**
	 *	Ends the exploration phase.
	 */
	public void endExploration() {
		removeControlButton();
	}

	/**
	 *	Begins the famine phase.
	 */
	public void beginFamine() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 8: Let each player place graves within his cities
	 *	if he does not have enough food. Granaries reduce famine by 3.
	 */
	public void performFamine() {
		final Player player = Antiquity.getPlayer();
		int starvation = player.starves();
		int phase = Antiquity.getPhase();
		int index = Antiquity.getTurn() + 1;
		String prefix = "msgPhase8";
		String format = String.format(Antiquity.get(prefix + "Text"), player.getName(), "%s");

		/* No graves? Continue! */
		if(starvation == 0) {
			Antiquity.log(String.format("player %s doesn't get new graves", player.getName()));
			setStatus(index, phase, String.format(format, Antiquity.get(prefix + "None")));
			Antiquity.nextTurn();
			return;
		}

		/* Too many graves? Game over! */
		int fields = 0;
		for(Citymap citymap: player.getCitymaps()) if(citymap.isActive()) fields += citymap.getSize() * citymap.getSize();
		fields -= player.hasBuilding(Grave.class);
		fields -= player.hasBuilding(House.class);
		if(fields <= starvation) {
			player.deactivate();
			player.starve(fields);
		}

		/* Create graves */
		ArrayList<Grave> graves = new ArrayList<Grave>();
		for(int j = 0; j < starvation; j++) {
			Grave grave = new Grave();
			graves.add(grave);
			player.addBuilding(grave);
			createCityBuilding(player, grave, false);
		}

		/* Automatic placement */
		int space = player.getSpace();
		boolean all = (!player.isActive() || starvation == space);
		if(space <= starvation) {
			GameObject[][] grid = player.createGrid();
			for(CityBuilding building: player.getBuildings(CityBuilding.class)) {
				if(!building.isActive()) continue;
				if(!player.isActive() && !(building instanceof Grave) && !(building instanceof House)) continue;
				Point location = building.getLocation();
				for(Point field: building.getFields()) {
					field.translate(location.x, location.y);
					grid[field.x][field.y] = null;
				}
			}
			for(int x = 0; x < grid.length; x++) {
				for(int y = 0; y < grid[x].length; y++) {
					if(grid[x][y] == null) continue;
					Grave grave = graves.remove(0);
					if(all) grave.activate();
					Point location = new Point(x, y);
					grave.update(location);
					grave.setMovable(false);
					grave.setBlocked(EmptySpace.getInstance());
				}
			}
			player.updateSpace(0);
		}

		/* Display message */
		if(player.isActive()) {
			String text = String.format(Antiquity.get(prefix + "New"), starvation, Antiquity.get("grave", starvation));
			if(space > 0 && space <= starvation) text += " " + String.format(Antiquity.get(prefix + "Auto"), space);
			setStatus(index, phase, String.format(format, text));
		} else {
			showWarning(String.format(Antiquity.get("dlgFamine"), player.getName(), fields, Antiquity.get("grave", starvation), starvation));
		}

		/* Continue if done */
		if(all) {
			Antiquity.nextTurn();
			return;
		}

		/* Create the control button */
		addControlButton(index);
		controlButton.addActionListener(
			new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if(!Antiquity.checkPlacement(player, true)) return;
					player.starve(0);
					Antiquity.log(String.format("player %s placed all graves correctly", player.getName()));
					Antiquity.nextTurn();
				}
			}
		);
	}

	/**
	 *	Ends the famine phase.
	 */
	public void endFamine() {
		removeControlButton();
	}

	/**
	 *	Begins the pollution phase.
	 */
	public void beginPollution() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 9: Let each player pollute his zone of control.
	 *	Players with dumps do not have to place 4 pollution chits and
	 *	their zone of control may not be polluted by other players.
	 */
	public void performPollution() {
		Player player = Antiquity.getPlayer();
		int garbage = Antiquity.getConstant("Garbage") * player.hasBuilding(City.class);
		if(player.hasBuilding(Dump.class) > 0) garbage -= Antiquity.getConstant("Dump");
		garbage -= player.hasBuilding(Fountain.class);
		int index = Antiquity.getTurn() + 1;
		int phase = Antiquity.getPhase();
		String prefix = "msgPhase" + phase;

		/* No garbage? Continue! */
		if(garbage <= 0) {
			Antiquity.log(String.format("player %s needs not to pollute", player.getName()));
			String text = String.format(Antiquity.get(prefix + "None"), player.getName());
			setStatus(index, phase, text);
			Antiquity.nextTurn();
			return;
		}

		/* Determine available hexes */
		ArrayList<Hexfield> illegal = new ArrayList<Hexfield>();
		int n = Antiquity.getPlayerCount();
		for(int i = 0; i < n; i++) {
			Player p = Antiquity.getPlayer(i);
			if(player != p && p.hasBuilding(Dump.class) > 0) illegal.addAll(p.getZoneOfControl());
		}
		final ArrayList<Hexfield> zoc = new ArrayList<Hexfield>();
		zoc.addAll(player.getZoneOfControl());
		for(int i = 0; i < zoc.size();) {
			Hexfield hex = zoc.get(i);
			if(hex.isPolluted() || hex.isUsed() || illegal.contains(hex)) {
				zoc.remove(hex);
			} else {
				i++;
			}
		}

		/* Show information */
		int space = zoc.size();
		String format = Antiquity.get(prefix + (space > 0 ? "New" : "Full"));
		setStatus(0, phase, String.format(format, player.getName(), garbage, Antiquity.get("space", garbage)));

		/* Automatic placement */
		if(space <= garbage) {
			for(Hexfield hex: zoc) hex.setPolluted(true);
			if(space < garbage) {
				garbage -= space;
				if(space > 0) {
					showInformation(
						String.format(
							Antiquity.get("dlgPollution"),
							player.getName(),
							space,
							Antiquity.get("space", space),
							garbage,
							Antiquity.get("grave", garbage)
						)
					);
				}
				tabbedPane.setSelectedIndex(index);
				player.starve(garbage);
				performFamine();
				return;
			} else {
				Antiquity.nextTurn();
				return;
			}
		}

		/* Create pollution observer */
		final int[] counter = {garbage};
		Observer observer = new Observer() {
			public void update(Observable obs, Object obj) {
				if(obj instanceof Integer && obj.equals(Antiquity.getCommand("Polluted"))) {
					Hexfield hex = (Hexfield)obs;
					hex.deleteObserver(this);
					hex.update(Antiquity.getCommand("RemoveMenu"));
					hex.deactivate();
					zoc.remove(hex);
					if(--counter[0] == 0) {
						for(Hexfield hexfield: zoc) {
							hexfield.deleteObserver(this);
							hexfield.update(Antiquity.getCommand("RemoveMenu"));
							hexfield.deactivate();
						}
						Antiquity.updateZoneOfControl(Antiquity.getPlayer());
						Antiquity.log(String.format("player %s is done with pollution", Antiquity.getPlayer().getName()));
						Antiquity.nextTurn();
					}
				}
			}
		};

		/* Let player pollute */
		for(Hexfield hex: zoc) {
			hex.addObserver(observer);
			hex.update(Antiquity.getCommand("AddMenu"));
			hex.activate();
		}
	}

	/**
	 *	Ends the pollution phase.
	 */
	public void endPollution() {}

	/**
	 *	Begins the check victory phase.
	 */
	public void beginCheckVictory() {
		if(guide) showGameGuidance(Antiquity.getPhase());
	}

	/**
	 *	Performs phase 10: Check if any player has won.
	 */
	public void performCheckVictory() {
		Antiquity.nextTurn();
	}

	/**
	 *	Ends the check victory phase.
	 */
	public void endCheckVictory() {}

	//================================================================================================= OTHER METHODS

	/**
	 *	Shows a dialog demanding the given player to pay the
	 *	given amounts of resources.
	 *	@param player the player
	 *	@param food the amount of food to pay
	 *	@param lux the amount of luxury goods to pay
	 */
	public void showResourceDialog(Player player, int food, int lux) {
		new ResourceDialog(this, player, food, lux);
	}

	/**
	 *	Shows a dialog demanding the given player to discard
	 *	the given amount of resources.
	 *	@param player the player
	 *	@param n the amount of resources
	 */
	public void showDiscardDialog(Player player, int n) {
		new DiscardDialog(this, player, n);
	}

	/**
	 *	Shows a dialog asking the given player to choose a house
	 *	he wants to get for free due to San Nicolo ability.
	 *	@param player the player
	 *	@param buildings the list of available houses
	 */
	public void showHouseDialog(Player player, ArrayList<House> houses) {
		Object choice = null;
		ArrayList<String> options = new ArrayList<String>();
		for(House house: houses) options.add(String.format(Antiquity.get("bld" + house), house.getIndex()));
		choice = JOptionPane.showInputDialog(
			this,
			Antiquity.get("dlgHouseMessage"),
			String.format(Antiquity.get("dlgHouseTitle"), player.getName()),
			JOptionPane.QUESTION_MESSAGE,
			GameUtilities.createIcon(player.getColor()),
			options.toArray(),
			options.get(0)
		);
		if(choice == null) return;
		for(House house: houses) {
			if(String.format(Antiquity.get("bld" + house), house.getIndex()).equals(choice)) {
				Antiquity.checkPurchase(player, house, true);
				return;
			}
		}
	}

	/**
	 *	Shows a dialog demanding the given player to choose
	 *	a saint from the given list.
	 *	@param player the player
	 *	@param building the cathedral object
	 *	@param saints the list of saints
	 */
	public void showSaintDialog(final Player player, Building building, Saint... saints) {
		Object choice = null;
		ArrayList<String> options = new ArrayList<String>();
		for(Saint saint: saints) options.add(Antiquity.get("st" + saint));
		while(true) {
			choice = JOptionPane.showInputDialog(
				this,
				Antiquity.get("dlgSaint"),
				building.toString(),
				JOptionPane.QUESTION_MESSAGE,
				GameUtilities.createIcon(player.getColor()),
				options.toArray(),
				options.get(0)
			);
			if(choice != null) break;
		}
		for(Saint saint: saints) {
			if(choice.equals(Antiquity.get("st" + saint))) {
				Antiquity.log(String.format("player %s has chosen %s for patron", player.getName(), saint));
				player.setSaint(saint);
				break;
			}
		}

		/* Show saint */
		Saint saint = player.getSaint();
		final JLabel label = GameUtilities.createLabel(saint, (Point)Antiquity.getProperty("CitySaintOffset"));
		cityPanes.get(player).add(label, Antiquity.getLayer("CitySaints"));
		final JLabel marker = GameUtilities.createMarker(new Worker(player.getColor()));
		marker.setPreferredSize(marker.getSize());
		marker.setToolTipText(String.format(Antiquity.get("ftSaintMarker"), Antiquity.get("st" + saint)));
		saintsPanels.get(saint.getType()).add(marker);
		player.getBuildings(Cathedral.class).get(0).addObserver(
			new Observer() {
				private JPanel saintsPanel = saintsPanels.get(player.getSaint().getType());
				private JLayeredPane cityPane = cityPanes.get(player);
				private Saint saint = null;

				public void update(Observable obs, Object obj) {
					/* Cathedral razed */
					if(obj instanceof Boolean && !(Boolean)obj) {
						((Cathedral)obs).update(CityBuilding.DISABLED);
						obs.deleteObserver(this);
						return;
					}

					/* Cathedral disabled/enabled */
					if(obj instanceof Integer) {
						if(obj.equals(CityBuilding.DISABLED)) {
							saint = player.getSaint();
							player.setSaint(null);
							cityPane.remove(label);
							cityPane.invalidate();
							cityPane.repaint();
							saintsPanel.remove(marker);
							saintsPanel.invalidate();
							saintsPanel.repaint();
							return;
						}

						if(obj.equals(CityBuilding.ENABLED)) {
							player.setSaint(saint);
							cityPane.add(label);
							saintsPanel.add(marker);
							return;
						}
					}
				}
			}
		);
	}

	/**
	 *	Shows a dialog demanding the given player to choose the
	 *	resource to produce from the given list and returns it.
	 *	@param player the player
	 *	@param building the producer object
	 *	@param resources the list of resources to choose from
	 *	@return the chosen resource
	 */
	public Resource showProducerDialog(Player player, Building building, Resource... resources) {
		Object choice = null;
		if(building instanceof Farm) {
			ArrayList<Resource> r = new ArrayList<Resource>();
			boolean biology = (player.hasBuilding(Biology.class) > 0);
			if(biology) {
				int use = JOptionPane.showConfirmDialog(
					this,
					Antiquity.get("dlgBiologyMessage"),
					Antiquity.get("dlgBiologyTitle"),
					JOptionPane.YES_NO_OPTION,
					JOptionPane.QUESTION_MESSAGE
				);
				if(use == JOptionPane.YES_OPTION) {
					player.getBuildings(Biology.class).get(0).setUsed(true);
				} else {
					biology = false;
				}
			}
			if(biology || player.hasResource(Grain.class) > 0) r.add(new Grain());
			if(biology || player.hasResource(Olives.class) > 0) r.add(new Olives());
			if(biology || player.hasResource(Sheep.class) > 0) r.add(new Sheep());
			if(biology || player.hasResource(Wine.class) > 0) r.add(new Wine());
			if(r.size() > 0) {
				resources = r.toArray(new Resource[]{});
			} else {
				return null;
			}
		}
		ArrayList<String> options = new ArrayList<String>();
		for(Resource resource: resources) options.add(Antiquity.get("res" + resource));
		if(options.size() > 1) {
			while(true) {
				choice = JOptionPane.showInputDialog(
					this,
					Antiquity.get("dlgProducer"),
					building.toString(),
					JOptionPane.QUESTION_MESSAGE,
					GameUtilities.createIcon(player.getColor()),
					options.toArray(),
					options.get(0)
				);
				if(choice != null) break;
			}
		} else {
			choice = options.get(0);
		}
		for(Resource resource: resources) {
			if(choice.equals(Antiquity.get("res" + resource))) {
				choice = resource;
				break;
			}
		}
		return (Resource)choice;
	}

	/**
	 *	Shows a dialog informing the current player which type
	 *	of resource he has found with his explorer.
	 *	@param player the player
	 *	@param building the explorer object
	 *	@param chit the exploration chit
	 */
	public void showExplorerDialog(Player player, Building building, ExplorationChit chit) {
		ResourceType type = chit.getType();
		Resource resource = Resource.createResource(type);
		createResource(player, resource);
		JOptionPane.showMessageDialog(
			this,
			String.format(
				Antiquity.get("dlgExplorerMessage"),
				player.getName(),
				Antiquity.get("res" + resource),
				Antiquity.get("dlgExplorer" + (type.equals(ResourceType.WINE) ? "Stay" : "Rise"))
			),
			building.toString(),
			JOptionPane.INFORMATION_MESSAGE,
			GameUtilities.createIcon(chit, type)
		);
	}

	/**
	 *	Shows a dialog asking the given player to give up the
	 *	game due to a complete lack of resources.
	 *	@param player the player
	 */
	public void showGiveUpDialog(Player player) {
		int choice = JOptionPane.showConfirmDialog(
			this,
			String.format(Antiquity.get("dlgGiveUpMessage"), player.getName()),
			Antiquity.get("dlgGiveUpTitle"),
			JOptionPane.YES_NO_OPTION,
			JOptionPane.QUESTION_MESSAGE
		);
		if(choice == JOptionPane.YES_OPTION) {
			Antiquity.log(String.format("player %s gives up", player.getName()));
			player.deactivate();
		}
	}

	/**
	 *	Shows the game end statistics.
	 *	@param ranks the final ranks
	 *	@param time the overall playing time
	 *	@param round the number of rounds
	 */
	public void showGameEndDialog(Rank[] ranks, long time, int round) {
		new GameEndDialog(this, ranks, time, round);
	}

	/**
	 *	Shows the game guidance message for the given phase.
	 *	@param phase the phase
	 */
	public void showGameGuidance(int phase) {
		JOptionPane.showMessageDialog(
			this,
			"<html>" + format(Antiquity.get("msgGuide" + phase)) + "</html>",
			String.format(Antiquity.get("dlgGuide"), phase, Antiquity.get("phase" + phase)),
			JOptionPane.INFORMATION_MESSAGE
		);
	}

	/**
	 *	Shows an error dialog with the given message.
	 *	@param message the message
	 */
	public void showError(String message) {
		showError(this, message);
	}

	/**
	 *	Shows a message dialog with the given message.
	 *	@param message the message
	 */
	public void showInformation(String message) {
		showInformation(this, message);
	}

	/**
	 *	Shows a warning dialog with the given message.
	 *	@param message the message
	 */
	public void showWarning(String message) {
		showWarning(this, message);
	}

	/**
	 *	Shows an error dialog with the given message.
	 *	@param message the message
	 */
	public void showError(Component owner, String message) {
		showDialog(owner, message, JOptionPane.ERROR_MESSAGE);
	}

	/**
	 *	Shows a message dialog with the given message.
	 *	@param message the message
	 */
	public void showInformation(Component owner, String message) {
		showDialog(owner, message, JOptionPane.INFORMATION_MESSAGE);
	}

	/**
	 *	Shows a warning dialog with the given message.
	 *	@param message the message
	 */
	public void showWarning(Component owner, String message) {
		showDialog(owner, message, JOptionPane.WARNING_MESSAGE);
	}

	/**
	 *	Shows a dialog of the given type with the given message
	 *	for the given source component.
	 *	@param owner the source component
	 *	@param message the message
	 *	@param type the type
	 */
	public void showDialog(Component owner, String message, int type) {
		JOptionPane.showMessageDialog(owner, message.startsWith("<html>") ? message: toHTML(message), Antiquity.get("attention"), type);
	}

	/**
	 *	Returns a formatted string that is not wider than a
	 *	standard width. Inserts HTML tags '<br>' where needed.
	 *	@param text the text to format
	 *	@return the formatted text
	 */
	public String format(String text) {
		int size = (Integer)Antiquity.getProperty("TextWidth");
		ArrayList<String> lines = new ArrayList<String>();
		StringTokenizer tok = new StringTokenizer(text, " ");
		FontMetrics fm = getFontMetrics(getFont());
		text = tok.nextToken();
		while(tok.hasMoreTokens()) {
			String word = tok.nextToken();
			if(fm.stringWidth(text + " " + word) > size) {
				lines.add(text);
				text = word;
			} else {
				text += " " + word;
			}
		}
		String ret = "";
		for(String line: lines) ret += line + "<br>";
		return ret + text;
	}

	//================================================================================================= UTILITY METHODS

	/**
	 *	Returns the given string formatted to a fixed text width
	 *	and written within '<html>' tags.
	 *	@param text the string
	 *	@return the formatted string
	 */
	private String toHTML(String text) {
		return "<html>" + format(text) + "</html>";
	}

	/**
	 *	Returns the tab with the given index.
	 *	@param index the index
	 *	@return the tab
	 */
	private JLayeredPane getTab(int index) {
		Component first = tabbedPane.getComponentAt(index);
		return (JLayeredPane)(first instanceof JScrollPane ? ((JScrollPane)first).getViewport().getView() : first);
	}

	/**
	 *	Displays the given messages in the status bar with the given index.
	 *	@param index the index
	 *	@param phase the phase
	 *	@param message the message
	 */
	private void setStatus(int index, int phase, String message) {
		statusBars.get(index).setText(String.format(Antiquity.get("ftPhase"), phase, Antiquity.get("phase" + phase), message));
	}

	/**
	 *	Creates a control button for the tab with the given index
	 *	and activates it after a short delay.
	 *	@param index the index
	 */
	private void addControlButton(int index) {
		if(controlButton != null) removeControlButton();
		controlButton = new JButton(Antiquity.get("btnReady"));
		controlButton.setBounds((Rectangle)Antiquity.getProperty("ControlBounds"));
		controlButton.setEnabled(false);
		getTab(index).add(controlButton, JLayeredPane.DEFAULT_LAYER);
		timer.restart();
	}

	/**
	 *	Removes the control button.
	 */
	private void removeControlButton() {
		if(controlButton != null) {
			JLayeredPane tab = JLayeredPane.getLayeredPaneAbove(controlButton);
			tab.remove(controlButton);
			tab.invalidate();
			tab.repaint();
			controlButton = null;
		}
	}

	/**
	 *	Disables the city button.
	 */
	private void disableCityButton() {
		if(!cityButton.isEnabled()) return;
		cityButton.putClientProperty("represents", null);
		cityButton.setIcon(null);
		cityButton.setEnabled(false);
		cityButton.setText(Antiquity.get("bldCity"));
	}

	/**
	 *	Enables the city button.
	 */
	private void enableCityButton() {
		Player player = Antiquity.getPlayer();
		if(player.hasBuilding(City.class) == Antiquity.getConstant("CityCount")) return;
		City city = new City(player.getColor());
		cityButton.putClientProperty("represents", city);
		cityButton.setIcon(GameUtilities.createIcon(city));
		cityButton.setEnabled(true);
		cityButton.setText(null);
	}

	/**
	 *	Disables the fisherman button.
	 */
	private void disableFishermanButton() {
		if(fishermanButton.isEnabled()) fishermanButton.setEnabled(false);
	}

	/**
	 *	Enables the fisherman button.
	 */
	private void enableFishermanButton() {
		if(((Limited)fishermanButton.getClientProperty("represents")).getTiles() > 0) fishermanButton.setEnabled(true);
	}

	/**
	 *	Updates the fisherman button tooltip text.
	 */
	private void updateFishermanButton() {
		Fisherman fisherman = (Fisherman)fishermanButton.getClientProperty("represents");
		fishermanButton.setToolTipText(GameUtilities.getDescription(fisherman, true));
		if(fisherman.getTiles() == 0) disableFishermanButton();
	}

	/**
	 *	Displays a warning about unplaced land buildings.
	 *	@return true if there are unplaced buildings
	 */
	private boolean checkUnplaced() {
		if(unplaced) showError(Antiquity.get("errUnplaced"));
		return unplaced;
	}

	/**
	 *	Creates the given land building for the active player.
	 *	@param building the building
	 */
	private DraggableLabel prepareLandBuilding(LandBuilding building) {
		unplaced = true;
		if(building instanceof City) {
			disableCityButton();
			enableCityButton();
		}
		final DraggableLabel tile = GameUtilities.createLabel(building, Antiquity.getPlayer());
		landPane.add(tile, Antiquity.getLayer("LandPlacement"));
		final MouseListener dblClick = new MouseAdapter() {
			public void mouseReleased(MouseEvent e) {
				LandBuilding building = (LandBuilding)tile.getClientProperty("represents");
				Point location = tile.getLocation();
				Point ref = GameUtilities.getReferencePoint(building);
				location.translate(ref.x, ref.y);
				building.update(LandSystem.getPoint(location));
			}

			public void mouseClicked(MouseEvent e) {
				if(e.getClickCount() > 1 && tile.isDraggable()) {
					((JMenuItem)tile.getComponentPopupMenu().getComponent(0)).doClick();
				}
			}
		};
		tile.addMouseListener(dblClick);
		building.addObserver(
			new Observer() {
				public void update(Observable obs, Object obj) {
					if(obj instanceof Boolean) {
						unplaced = false;
						tile.setDraggable(false);
						tile.resetIcon();
						tile.removeMouseListener(dblClick);
						landPane.setLayer(tile, Antiquity.getLayer("LandBuildings"));
						landPane.moveToFront(tile);
						obs.deleteObserver(this);
					}

					if(obj instanceof Point) {
						Point location = (Point)obj;
						Point pixel = LandSystem.getPixel(location);
						Point ref = GameUtilities.getReferencePoint(obs);
						pixel.translate(-ref.x, -ref.y);
						tile.setLocation(pixel);
					}
				}
			}
		);
		if(building instanceof Fisherman) {
			building.addObserver(
				new Observer() {
					public void update(Observable obs, Object obj) {
						if(obj instanceof Boolean && !(Boolean)obj) {
							((Limited)obs).decreaseTiles(-1);
							landPane.remove(tile);
							landPane.invalidate();
							landPane.repaint();
							updateFishermanButton();
							obs.deleteObserver(this);
						}
					}
				}
			);
		}
		return tile;
	}
}